package incrpatch

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// SetValueReflect 通过反射的方法对一个字段进行赋值
func SetValueReflect(refValue reflect.Value, value interface{}) error {
	if !refValue.CanAddr() {
		return fmt.Errorf(`尝试向 %v 类型字段赋值失败，字段为Unaddressable`, refValue.Kind())
	}
	t := reflect.ValueOf(value)
	if t.Kind() == reflect.Interface {
		t = t.Elem()
	}

	// 如果 t 是无效值，则将字段赋为其类型对应的 0 值
	if !t.IsValid() {
		t = reflect.Zero(refValue.Type())
		refValue.Set(t)
		return nil
	}

	if t.Kind() == reflect.Ptr {
		if refValue.Kind() != reflect.Ptr && refValue.Kind() != reflect.Interface {
			refValue.Set(t.Elem())
			return nil
		}
		refValue.Set(t)
		return nil
	}

	if refValue.Kind() == reflect.Ptr || refValue.Kind() == reflect.Interface {
		if t.CanAddr() {
			refValue.Set(t.Addr())
			return nil
		}
		nt := reflect.New(t.Type())
		nt.Elem().Set(t)
		refValue.Set(nt)
		return nil
	}

	refValue.Set(t)
	return nil
}

// FindChildValueReflect 通过反射方法在 parent 结构中查找 keySeqs 指定的子节点字段的反射值。
// parent 必须是 map 或者 struct
func FindChildValueReflect(parent reflect.Value, keySeqs []string) (reflect.Value, bool) {
	if len(keySeqs) == 0 {
		return parent, true
	}
	if parent.Kind() == reflect.Interface {
		parent = parent.Elem()
	}
	for parent.Kind() == reflect.Ptr {
		parent = parent.Elem()
	}
	if !parent.IsValid() {
		return reflect.ValueOf(nil), false
	}

	// 若父节点是 struct，则查找其成员变量名称匹配 keySeqs 中的第一个
	if parent.Kind() == reflect.Struct {
		for j := 0; j < parent.NumField(); j++ {
			// The Type's StructField for a given field is checked to see if StructField.PkgPath
			// is set to determine if the field is exported or not because CanSet() returns false
			// for settable fields.  I'm not sure why.  -mohae
			if parent.Type().Field(j).PkgPath != "" {
				continue
			}
			childName := parent.Type().Field(j).Name
			if strings.ToLower(childName) == keySeqs[0] {
				return FindChildValueReflect(parent.Field(j), keySeqs[1:])
			}
		}
		return reflect.ValueOf(nil), false
	}

	// 若父节点是 map，则查找其中 key 为 keySeqs 中第一个的元素
	if parent.Kind() == reflect.Map {
		lastv := parent.MapIndex(reflect.ValueOf(keySeqs[0]))
		if !lastv.IsValid() {
			return reflect.ValueOf(nil), false
		}
		return FindChildValueReflect(lastv, keySeqs[1:])
	}

	// 若父节点是数组或者切片，且 keySeqs 中第一段是数字，则以其为下标查找数组或切片元素
	if parent.Kind() == reflect.Array ||
		parent.Kind() == reflect.Slice {
		index, err := strconv.Atoi(keySeqs[0])
		if err != nil {
			return reflect.ValueOf(nil), false
		}
		if index < 0 || index >= parent.Len() {
			return reflect.ValueOf(nil), false
		}
		return FindChildValueReflect(parent.Index(index), keySeqs[1:])
	}

	return reflect.ValueOf(nil), false
}

// SetChildValueReflect 通过反射方法在 parent 的结构中设置其 keySeqs 指定的子节点的值
func SetChildValueReflect(parent reflect.Value, keySeqs []string, value interface{}) ([]string, error) {
	// 已经到达最后以及节点，直接通过当前的反射值来赋值
	if len(keySeqs) == 0 {
		return nil, SetValueReflect(parent, value)
	}
	var trimKey []string
	// 如果当前还不是最后一级节点，则需要对当前的反射值解引用，找到真实的 {复合类型} 字段数据实体
	if parent.Kind() == reflect.Interface {
		parent = parent.Elem()
	}
	for parent.Kind() == reflect.Ptr {
		if parent.IsNil() {
			trimKey = keySeqs
			parent.Set(reflect.New(parent.Type().Elem()))
		}
		parent = parent.Elem()
	}
	if !parent.IsValid() {
		return nil, fmt.Errorf(`向 %v 类型字段的成员 %v 赋值失败，字段值为空`, parent.Kind(), strings.Join(keySeqs, `.`))
	}

	if parent.Kind() == reflect.Struct {
		for j := 0; j < parent.NumField(); j++ {
			// The Type's StructField for a given field is checked to see if StructField.PkgPath
			// is set to determine if the field is exported or not because CanSet() returns false
			// for settable fields.  I'm not sure why.  -mohae
			if parent.Type().Field(j).PkgPath != "" {
				continue
			}
			childName := parent.Type().Field(j).Name
			if strings.ToLower(childName) == keySeqs[0] {
				k, err := SetChildValueReflect(parent.Field(j), keySeqs[1:], value)
				if len(trimKey) > 0 {
					return trimKey, err
				}
				return k, err
			}
		}
		return nil, fmt.Errorf(`向 %v 类型字段的成员 %v 赋值失败，找不到此成员`, parent.Type(), strings.Join(keySeqs, `.`))
	}

	if parent.Kind() == reflect.Map {
		if parent.IsNil() {
			trimKey = keySeqs
			parent.Set(reflect.MakeMap(parent.Type()))
		}
		// 要更改的字段是一个 map 的直接成员，则直接修改 map 的 key-value
		if len(keySeqs) == 1 {
			t := reflect.ValueOf(value)
			if t.Kind() == reflect.Interface {
				t = t.Elem()
			}
			if parent.Type().Elem().Kind() == reflect.Ptr && t.Kind() != reflect.Ptr && t.IsValid() {
				nt := reflect.New(t.Type())
				nt.Elem().Set(t)
				t = nt
			}
			parent.SetMapIndex(reflect.ValueOf(keySeqs[0]), t)
			return trimKey, nil
		}
		child := parent.MapIndex(reflect.ValueOf(keySeqs[0]))
		if !child.IsValid() {
			return nil, fmt.Errorf(`向 %v 类型字段的成员 %v 赋值失败，找不到此成员`, parent.Type(), strings.Join(keySeqs, `.`))
		}
		k, err := SetChildValueReflect(child, keySeqs[1:], value)
		if len(trimKey) > 0 {
			return trimKey, err
		}
		return k, err
	}

	// 若父节点是数组或者切片，且 keySeqs 中第一段是数字，则以其为下标向数组或切片元素赋值
	if parent.Kind() == reflect.Array ||
		parent.Kind() == reflect.Slice {
		index, err := strconv.Atoi(keySeqs[0])
		if err != nil {
			return nil, err
		}
		if index < 0 || index >= parent.Len() {
			return nil, fmt.Errorf(`向 %v 类型字段的成员 %v 赋值失败，下标越界`, parent.Type(), strings.Join(keySeqs, `.`))
		}
		k, err := SetChildValueReflect(parent.Index(index), keySeqs[1:], value)
		if len(trimKey) > 0 {
			return trimKey, err
		}
		return k, err
	}

	return nil, fmt.Errorf(`向 %v 类型字段的成员 %v 赋值失败，字段不是一个{复合类型}`, parent.Type(), strings.Join(keySeqs, `.`))
}
